#include "common.h"

#define MAXBUFLEN 1024
#define INTERFACENAMELEN 255

#define DHCPACK 5
#define DHCPINFORM 8
#define RR_NAME "_nac-server._udp"
#define BROADCAST "255.255.255.255"
#define MAXBUFLEN 1024
#define SERVERPORT "6867"
#define CLTPORT 6868
#define INTERFACENAMELEN 255

#define DHO_NDN_NAC 246
#define DHO_NDN_GATEWAY 247
#define DHO_NDN_NAMESPACE 248

#define NACSERVER 0
#define NDNPARA 1

#define TIMEOUT 3
#define MAXRETRYTIME 3

FILE *LOG;

struct prefixes{
    char content[255];
    struct prefixes *next;
};

struct nac{
    unsigned afi;
    char ip[INET6_ADDRSTRLEN];
    struct nac *next;
};

struct ndngw{
    char addr[INET6_ADDRSTRLEN];
    char port[10];
    unsigned protocol;
    struct prefixes *nameprefix;
    struct ndngw *next;
};

struct mydata{
    struct ndngw *gws;
    struct prefixes *namelist;
    struct nac *naclist;
    struct dhcp_packet *packet;
    int packet_len;
    int state;
    char *myip;
    char *dst;
    char *myport;
    char *domain;
    struct hardware *hw;
    u_int32_t my_xid;
};

int send_inform(struct mydata *md);

void
log_info(FILE *dst, const char *fmt, ...){
    if (dst == NULL)
        return;

    char log[1024];
    va_list ap;
    struct timeval t;
    gettimeofday(&t, NULL);

    va_start(ap, fmt);
    vsprintf(log, fmt, ap);
    va_end(ap);

    fprintf(dst, "%d.%06d, nac_agent[%d]: %s\n", (int)t.tv_sec, (unsigned)t.tv_usec, (int)getpid(), log);    
}

void
free_nacs(struct mydata *md){
    struct nac *cur = md->naclist, *tmp;
    if(cur == NULL)
        return;
    while(cur != NULL){
        tmp = cur->next;    
        cur->next = NULL;
        free(cur);
        cur = tmp;
    }
    md->naclist = NULL;
}

void
free_namespaces(struct mydata *md){
    struct prefixes *cur = md->namelist, *tmp;
    if(cur == NULL)
        return;

    while(cur != NULL){
        tmp = cur->next;    
        cur->next = NULL;
        free(cur);
        cur = tmp;
    }

    md->namelist = NULL;
}

void
free_gws(struct mydata *md){
    struct ndngw *cur = md->gws, *tmp;
    if(cur == NULL)
        return;

    while(cur != NULL){
        tmp = cur->next;    
        cur->next = NULL;

        /*free all the prefix belongs to this gateway*/
        struct prefixes *pp = cur->nameprefix, *temp;
        if (pp == NULL)
            continue;
        while (pp != NULL){
            temp = pp->next;
            pp->next = NULL;

            free(pp);
            pp = temp;
        }
      
        free(cur);
        cur = tmp;
    }

    md->gws = NULL;
}

void
print_nacs(struct mydata *md){
    struct nac *l = md->naclist;
    printf("\n\n*******<NAC servers>*******\n");
    while(l != NULL){
        printf("[NAC addr] %s\n", l->ip);
        l = l->next;
    }
    printf("\n");
}

void
print_gws(struct mydata *md){
    struct ndngw *cur = md->gws;
    printf("\n\n*******<NDN Gateways>*******\n");

    while(cur != NULL){
        printf("(Gateway) %s:%s\n", cur->addr, cur->port);

        /*print all the prefix belongs to this gateway*/
        struct prefixes *pp = cur->nameprefix;
        if (pp == NULL)
            continue;

        while (pp != NULL){
            printf("[prefix] %s\n",pp->content);
            pp = pp->next;
        }
        /*prefix free ends*/
      
        cur = cur->next;
        printf("\n");
    }
    printf("\n");
}

void
print_namespace(struct mydata *md){
    printf("\n\n*******<NDN Namespaces>*******\n");
    struct prefixes *namespace = md->namelist;
    while(namespace != NULL){
        printf("(Namespace) %s\n", namespace->content);
        namespace = namespace->next;
    }
    printf("\n");
}

int
try_nacs(struct mydata *md){
    if(md == NULL || md->myip == NULL)
        return 1;

    int res = 1;
    md->state = NDNPARA;//unicast
    struct nac *cur = md->naclist;
    while(cur != NULL){
        md->dst = cur->ip;
        
        log_info(LOG, "Try to get NDN paras from NAC server %s", md->dst);
        res = send_inform(md);
        if (res == 0)
            break;

        cur = cur->next;
    }

    return res;
}

//this func is only for packet not more than MAX
int is_valid_inform_reply(struct dhcp_packet *packet, int len, struct mydata *md){
    //if the transaction ID is wrong, or it is not the reply, it is invalid
    if(packet->xid != md->my_xid || packet->op != BOOTREPLY)
        return -1;

    //if the msgType is not ACK, it is invalid
    unsigned char options[DHCP_MAX_OPTION_LEN];
    memcpy(options, packet->options, DHCP_MAX_OPTION_LEN);

    //parse options here
    int opt_len = len - DHCP_FIXED_NON_UDP;
    //the first 4 is the magic cookie
    int index = 4;
    u_int8_t code, l;
    while(index < opt_len - 1){//the last one is the END(255)
        code = options[index];
        if(code == DHO_END)//this is the end
            return -1;

        index ++;
        l = options[index];
        index ++;

        if(code == DHO_DHCP_MESSAGE_TYPE && options[index] == DHCPACK)
            return 0;//this is the valid msg

        index = index + l;//after the last option
    }
    return -1;
}

// get sockaddr, IPv4 or IPv6:
void *get_in_addr(struct sockaddr *sa)
{
    if (sa->sa_family == AF_INET) {
        return &(((struct sockaddr_in*)sa)->sin_addr);
    }

    return &(((struct sockaddr_in6*)sa)->sin6_addr);
}

//convert a struct sockaddr addr to a string, IPv4 and IPv6
char *
get_ip_str(const struct sockaddr *sa, char *s, size_t maxlen){
    switch(sa->sa_family){
        case AF_INET:
            inet_ntop(AF_INET, &(((struct sockaddr_in *)sa)->sin_addr),
                    s, maxlen);
            break;

        case AF_INET6:
            inet_ntop(AF_INET6, &(((struct sockaddr_in6 *)sa)->sin6_addr),
                    s, maxlen);
            break;

        default:
            strncpy(s, "unknown AF", maxlen);
            return NULL;
    }

    return s;
}



int
parse_nacs(struct mydata *md, unsigned char *options, 
        int index, int l){
    int offset = 0;
    unsigned char *nac_buf = (unsigned char*)malloc(l);
    memcpy(nac_buf, options + index, l);

    unsigned count = nac_buf[offset];
    offset ++;
    unsigned i = 0;

    while (i < count){
        if (offset >= l)
            break;
        struct nac *nacserver = md->naclist;
        if(nacserver == NULL){//make sure that the 1st one has space
            //malloc a new space for new nac
            nacserver = (struct nac*)malloc(sizeof (struct nac));
            nacserver->next = NULL;
            md->naclist = nacserver;
        }else{                 
            struct nac *prev = NULL;
            //find the right position to add a new nac
            while(nacserver != NULL){
                prev = nacserver;
                nacserver = nacserver->next;
            }
            //malloc a new space for new nac
            nacserver = (struct nac*)malloc(sizeof (struct nac));
            nacserver->next = NULL;
            prev->next = nacserver;
        }

        unsigned afi = nac_buf[offset];
        offset ++;

        struct sockaddr *sa = NULL;
        if(afi == 0){//this is ip v4
            struct sockaddr_in nac_addr;
            nac_addr.sin_family = AF_INET;
        
            memcpy(&(nac_addr.sin_addr), nac_buf + offset, sizeof(struct in_addr));
            sa = (struct sockaddr*)&nac_addr;
            get_ip_str(sa, nacserver->ip, INET_ADDRSTRLEN);
            nacserver->afi = afi;

            offset += sizeof (struct in_addr);
        }

        if(afi == 1){//ipv6
            struct sockaddr_in6 nac_addr;
            nac_addr.sin6_family = AF_INET6;
        
            memcpy(&(nac_addr.sin6_addr), nac_buf + offset, sizeof(struct in6_addr));
            sa = (struct sockaddr*)&nac_addr;
            get_ip_str(sa, nacserver->ip, INET6_ADDRSTRLEN);
            nacserver->afi = afi;

            offset += sizeof (struct in6_addr);
        }

        i ++;
    }

    free(nac_buf);

    if(offset != l){//only when offset = l, the option is valid
        free_nacs(md);
        return 1;
    }
    return 0;
}

int
parse_gws(struct mydata *md, unsigned char *options, 
        int index, int l){
    int offset = 0;
    unsigned char *ndn_buf = (unsigned char*)malloc(l);
    memcpy(ndn_buf, options + index, l);
    unsigned gw_count = ndn_buf[offset];
    offset ++;

    unsigned i = 0;
    while(i < gw_count){
        struct ndngw *gws = md->gws, *prev;
        unsigned protocol = ndn_buf[offset];
        offset ++;
        unsigned afi = ndn_buf[offset];
        offset ++;

        if(gws == NULL){//make sure that the 1st one has space
            //malloc a new space for new gw
            gws = (struct ndngw*)malloc(sizeof (struct ndngw));
            gws->next = NULL;
            gws->nameprefix = NULL;
            md->gws = gws;
        }else{                 
            prev = NULL;
            //find the right position to add a new gw
            while(gws != NULL){
                prev = gws;
                gws = gws->next;
            }
            //malloc a new space for new gw
            gws = (struct ndngw*)malloc(sizeof (struct ndngw));
            gws->next = NULL;
            gws->nameprefix = NULL;
            prev->next = gws;
        }
        gws->protocol = protocol;
        struct sockaddr *sa;
        if(afi == 0){
            struct sockaddr_in gw_addr;
            gw_addr.sin_family = AF_INET;
        
            memcpy(&(gw_addr.sin_addr), ndn_buf + offset, sizeof(struct in_addr));
            sa = (struct sockaddr*)&gw_addr;
            get_ip_str(sa, gws->addr, INET_ADDRSTRLEN);

            offset += sizeof (struct in_addr);
        }

        if(afi == 1){
            struct sockaddr_in6 gw_addr;
            gw_addr.sin6_family = AF_INET6;
        
            memcpy(&(gw_addr.sin6_addr), ndn_buf + offset, sizeof(struct in6_addr));
            sa = (struct sockaddr*)&gw_addr;
            get_ip_str(sa, gws->addr, INET6_ADDRSTRLEN);
            offset += sizeof (struct in6_addr);
        }

        /*handle port number*/
        uint16_t gw_port;
        memcpy(&gw_port, ndn_buf + offset, 2);//port
        offset += 2;
        sprintf(gws->port, "%d", ntohs(gw_port));

        /*namespaces*/
        uint16_t name_num;
        memcpy(&name_num, ndn_buf + offset, 2);//name_num
        offset += 2;

        int j = 0;
        while(j < ntohs(name_num)){
            /*always make sure there is a space to store the name*/
            struct prefixes *pos = gws->nameprefix;
            if(pos == NULL){
                pos = (struct prefixes*)malloc(sizeof (struct prefixes));
                pos->next = NULL;
                gws->nameprefix = pos;
            }else{
                struct prefixes *last;
                while(pos != NULL){
                    last = pos;
                    pos = pos->next;
                }
                pos = (struct prefixes*)malloc(sizeof (struct prefixes));
                pos->next = NULL;
                last->next = pos;
            }

            uint16_t name_len;// = ndn_buf[offset];
            memcpy(&name_len, ndn_buf + offset, 2);//name_len
            offset += 2;

            if ((offset + ntohs(name_len)) <= l)
                memcpy(pos->content, ndn_buf + offset, ntohs(name_len));
            else
                goto out;

            pos->content[ntohs(name_len)] = '\0';
            offset += ntohs(name_len);

            j ++;
        }
        i ++;
    }

out:
    free(ndn_buf);
    if(offset != l){//only when offset = l, the option is valid
        free_gws(md);
        return 1;
    }

    return 0;
}

int
parse_namespaces(struct mydata *md, unsigned char *options, 
        int index, int l){
    struct prefixes *name = md->namelist;
    if (name == NULL){
        name = (struct prefixes *)malloc(sizeof (struct prefixes));
        name->next = NULL;
        md->namelist = name;
    }
    memcpy(name->content, options + index, l);
    name->content[l] = '\0';
    //printf("namespace: %s %p %p\n", name->content, name, md->namelist);
    return 0;
}



//target indicating the parsed option, 0=nacserver, 1=ndngw
int
parse_options(struct dhcp_packet *packet, struct mydata *md){
    int target = md->state, res = 1; 
    int flag = -1;//successful?
    int flag_gateway = 0;//indicating whether we get the NDN option
    int flag_name = 0;//indicating whether we get the domain option
    int flag_nacserver = 0;
    unsigned char options[DHCP_MAX_OPTION_LEN];
    //struct ndn_data inform_reply;
    memcpy(options, packet->options, DHCP_MAX_OPTION_LEN);

    //I only care 246 and Domain name
    //the first 4 is the magic cookie
    int index = 4;
    unsigned code, l;//l is the length of the value
    while(index < DHCP_MAX_OPTION_LEN - 1){//the last one is the END(255)
        code = options[index];
        if(code == DHO_END)//this is the end
            break;

        index ++;
        l = options[index];
        index ++;

        if(code == DHO_NDN_NAC){
            res = parse_nacs(md, options, index, l);
            if (res == 1)
                return res;
            flag_nacserver = 1;
        }

        if(code == DHO_NDN_NAMESPACE){//namespace
            parse_namespaces(md, options, index, l);
            flag_name = 1;
        }

        if(code == DHO_NDN_GATEWAY){//ndn, now the format must be
            //code Len(1), count (1), AFI(1), IP(4 or 6), Port(2), NameNo(2)
            //Name Len(2), Nameprefix
            res = parse_gws(md, options, index, l);
            if (res == 1)
                return res;
            flag_gateway = 1;
        }

        index = index + l;//after the last option
    }

    if(flag_gateway == 1 && flag_name == 1 && target == NDNPARA){
        //save the parameters in a file
        //save_para(&inform_reply);
        flag = 0;
    }
    if(flag_nacserver == 1 && target == NACSERVER){
        flag = 0;
    }
    return flag;
}


int
make_options(struct dhcp_packet *packet, int flag){
    int offset = 0;
    //construct options here
    unsigned char options[DHCP_MAX_OPTION_LEN];
    memset(options, 0, DHCP_MAX_OPTION_LEN);

    //cpy the magic cookie(start of options)
    memcpy(options, DHCP_OPTIONS_COOKIE, 4);
    offset += 4;

    //option 53, msg Type, we must contain this one
    struct option_header oh;
    oh.code = DHO_DHCP_MESSAGE_TYPE;
    oh.len = 1;
    memcpy(options + offset, &oh, sizeof (struct option_header));
    offset += sizeof (struct option_header);
    options[offset] = DHCPINFORM; 
    offset ++;

    //option 55, request list
    memset(&oh, 0, sizeof (struct option_header));
    oh.code = DHO_DHCP_PARAMETER_REQUEST_LIST;
    if (flag == NACSERVER){
        oh.len = 1;//only need 246(NAC server)
        memcpy(options + offset, &oh, sizeof (struct option_header));
        offset += sizeof (struct option_header);
        options[offset] = DHO_NDN_NAC;//this one should be the NDN option
        offset ++;
    }else if (flag == NDNPARA){
        oh.len = 2;//right now, we need name and gw
        memcpy(options + offset, &oh, sizeof (struct option_header));
        offset += sizeof (struct option_header);
        options[offset] = DHO_NDN_NAMESPACE;//this one should be the NDN option
        offset ++;
        options[offset] = DHO_NDN_GATEWAY;
        offset ++;
    }
    //the end of options
    options[offset] = DHO_END;
    offset ++;

    //make the packet as word boundary
    if(offset % 4 != 0){
        offset = offset + 4 - (offset % 4);
    }
    memcpy(packet->options, options, offset);

    return offset;
}


void 
make_common_parts(struct dhcp_packet *packet, char *myip, struct mydata *md){
    int rv;
    //get the hardware address by ip
    char face_name[INTERFACENAMELEN];
    get_face_name(face_name, myip);
    struct hardware myhw;
    get_hw_addr(face_name, &myhw);

    //assign value
    packet->op = BOOTREQUEST;
    packet->htype = myhw.hbuf[0]; //assuming it is a ethernet card
    packet->hlen = myhw.hlen - 1;//assuming it is ethernet card
    packet->hops = 0;
    packet->xid = md->my_xid;
    packet->secs = 0;
    packet->flags = 0;//assuming this interface can use unicast way, but we need another mechanism to test, and then set it
    memset(&(packet->ciaddr), 0, sizeof (packet->ciaddr));
    
    //assign my current ip
    struct in_addr ciaddr;
    if((rv = inet_aton(myip, &ciaddr)) == 0){
        log_info(LOG, "Cannot convert IP");
        exit(1);
    }
    packet->ciaddr = ciaddr;

    //to us, those parts are 0
    memset(&(packet->yiaddr), 0, sizeof (packet->yiaddr));
    memset(&(packet->siaddr), 0, sizeof (packet->siaddr));
    memset(&(packet->giaddr), 0, sizeof (packet->giaddr));

    //cpy the hardware address
    memset(&(packet->chaddr), 0, sizeof (packet->chaddr));
    if(myhw.hlen > 0)
        memcpy(&(packet->chaddr), &(myhw.hbuf[1]), (unsigned)(myhw.hlen - 1));
}



void
init_packet(struct dhcp_packet *packet){
    memset(packet, 0, sizeof (struct dhcp_packet));
}


void
set_sock_opt(int sockfd, struct sockaddr *sa){
    int broadcast = 1, reuse = 1;
    // this call is what allows broadcast packets to be sent:
    if (setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &broadcast,
        sizeof broadcast) == -1) 
    {
        log_info(LOG, "setsockopt (SO_BROADCAST)");
        exit(1);
    }

    // this call is what allows reuse port:
    if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse,
        sizeof reuse) == -1) 
    {
        log_info(LOG, "setsockopt (SO_REUSEADDR)");
        exit(1);
    }

#if defined(__APPLE__) && defined(__MACH__)
    int port = 1;
    if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, &port,
        sizeof port) == -1) 
    {
        log_info(LOG, "setsockopt (SO_REUSEPORT)");
        exit(1);
    }
#endif
    
    if( -1 == bind(sockfd, (struct sockaddr *)sa, sizeof(struct sockaddr)) ){
        log_info(LOG, "Cannot bind socket: %s", strerror(errno));
        exit(1);
    }
}

//Convert a string to a struct sockaddr address, IPv4 and IPv6:
void get_sockaddr(const struct sockaddr *sa, char *ip)
{
    switch(sa->sa_family) {
        case AF_INET:
            inet_pton(AF_INET, ip, &(((struct sockaddr_in *)sa)->sin_addr));
            //(((struct sockaddr_in *)sa)->sin_addr).s_addr = INADDR_ANY;
            ((struct sockaddr_in *)sa)->sin_port = htons(CLTPORT);
            memset(((struct sockaddr_in *)sa)->sin_zero, '\0', sizeof (((struct sockaddr_in *)sa)->sin_zero));
            break;

        case AF_INET6:
            inet_pton(AF_INET6, ip, &(((struct sockaddr_in6 *)sa)->sin6_addr));
            ((struct sockaddr_in6 *)sa)->sin6_port = htons(CLTPORT);
            break;

        default:
            log_info(LOG, "Unknown AF");
            exit(1);
    }
}

int get_ip_type(char *ip){
    if (ip == NULL)
        return -1;//empty

    int len = strlen(ip);
    int i = 0;
    while (i < len){
        if (ip[i] == ':')
            return 1;//v6
        i ++;
    }

    return 0;//v4
}

//need local ip and structure to store info
int 
send_inform(struct mydata *md){
    char *myip = md->myip;
    char *srv = md->dst;
    if(myip == NULL || srv == NULL)
        return 1;
    int sockfd;
    struct addrinfo hints, *servinfo, *p;
    int rv, retry = 0;
    int numbytes;
    char buf[MAXBUFLEN];

    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_DGRAM;

    if ((rv = getaddrinfo(srv, SERVERPORT, &hints, &servinfo)) != 0) {
        log_info(LOG, "getaddrinfo: %s", gai_strerror(rv));
        return 1;
    }

    // loop through all the results and make a socket
    for(p = servinfo; p != NULL; p = p->ai_next) {
        if ((sockfd = socket(p->ai_family, p->ai_socktype,
                p->ai_protocol)) == -1) {
            continue;
        }
        break;
    }

    if (p == NULL) {
        log_info(LOG, "failed to bind socket");
        return 1;
    }

    //convert the src IP to a structure
    int ip_type = get_ip_type(myip);
    if (ip_type == -1){
        log_info(LOG, "You input a wrong IP address");
        exit(1);
    }

    struct sockaddr sa;
    if (ip_type == 0){//v4
        sa.sa_family = AF_INET;
    }else if (ip_type == 1){
        sa.sa_family = AF_INET6;
    }
    get_sockaddr(&sa, myip);

    //let the socket can listening on 68, and broadcast
    set_sock_opt(sockfd, &sa);

    //variables will be used
    //create the dhcp packet structure
    struct dhcp_packet out_packet;
    init_packet(&out_packet);

    int offset, msg_len;

    if(md->state == 0){
        //generate the transaction no.
        md->my_xid = rand();

        //make the common parts, such as xid, ciadr et al.
        make_common_parts(&out_packet, myip, md);
        
        //make the options part, but right now only support domain name and private 246
        offset = make_options(&out_packet, NACSERVER);
        //DHCP_FIXED_NON_UDP includes all the field from op to the end of file, offset is the length of options
        msg_len = DHCP_FIXED_NON_UDP + offset;
    }else{
        //cpy the received packet to out_packet
        //offset = make_options(&out_packet, NDNPARA);
        memcpy(&out_packet, md->packet, md->packet_len);
        msg_len = md->packet_len;
    }
redo:
    memset(buf, 0, MAXBUFLEN);
    memcpy(buf, &out_packet, msg_len);

    log_info(LOG, "Send DHCPINFORM msg from %s to %s", myip, srv);
    //send packet
    if ((numbytes = sendto(sockfd, buf, msg_len, 0, p->ai_addr, p->ai_addrlen)) == -1) {
        log_info(LOG, "failed to send data");
        close(sockfd);
        return 1;
    }

    //read from remote server, maybe we need to get its addr for the latter use
    struct sockaddr_storage their_addr;
    socklen_t addr_len = sizeof their_addr;
wait:
    if(readable_time(sockfd, TIMEOUT) == 0){//need to add retry
        log_info(LOG, "timeout ...");
        if(retry < MAXRETRYTIME){
            retry ++;
            goto redo;
        }
    }else{
           memset(buf, 0, MAXBUFLEN);
        if ((numbytes = recvfrom(sockfd, buf, MAXBUFLEN, 0, (struct sockaddr *)&their_addr, &addr_len)) == -1) {
            log_info(LOG, "receive failed");
            close(sockfd);
            return 1;
        }

        //get src addr
        char src_addr[INET6_ADDRSTRLEN];

        if (their_addr.ss_family == AF_INET){
            struct sockaddr_in *sock = (struct sockaddr_in *)&their_addr;
            inet_ntop(AF_INET, &(sock->sin_addr), src_addr, INET_ADDRSTRLEN);
        }

        if (their_addr.ss_family == AF_INET6){
            struct sockaddr_in6 *sock = (struct sockaddr_in6 *)&their_addr;
            inet_ntop(AF_INET6, &(sock->sin6_addr), src_addr, INET6_ADDRSTRLEN);
        }

        struct dhcp_packet in_packet;
        init_packet(&in_packet);
        memcpy(&in_packet, buf, numbytes);

        //check if this message is the right ACK, on the same port, dhclient may send out request and get ack or offer
        if((rv = is_valid_inform_reply(&in_packet, numbytes, md)) == 0){
            log_info(LOG, "valid ACK message from %s", src_addr);
            rv = parse_options(&in_packet, md);
            if(rv != 0)//when md->state is 0, we need a timer to say if it is time out
                goto wait;
            //copy the packet to md
            if (md->state == NDNPARA){//unicast
                //md->packet = (struct dhcp_packet *)malloc(sizeof(struct dhcp_packet));
                init_packet(md->packet);
                memcpy(md->packet, buf, numbytes);
                md->packet_len = numbytes;
            }
        }
        else{
            log_info(LOG, "invalid ACK message from %s", src_addr);
            goto wait;
        }
    }
    
//out:
    freeaddrinfo(servinfo);
    close(sockfd);

    if(retry >= MAXRETRYTIME)
        return 1;

    return 0;
}



/*
//need local ip and structure to store info
int 
send_inform(struct mydata *md){
    char *myip = md->myip;
    char *srv = md->dst;
    if(myip == NULL || srv == NULL)
        return 1;
    int sockfd;
    struct addrinfo hints, *servinfo, *p;
    int rv, retry = 0;
    int numbytes;
    char buf[MAXBUFLEN];

    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_DGRAM;
    if ((rv = getaddrinfo(srv, SERVERPORT, &hints, &servinfo)) != 0) {
        log_info(LOG, "getaddrinfo: %s", gai_strerror(rv));
        return 1;
    }

    // loop through all the results and make a socket
    for(p = servinfo; p != NULL; p = p->ai_next) {
        if ((sockfd = socket(p->ai_family, p->ai_socktype,
                p->ai_protocol)) == -1) {
            continue;
        }
        break;
    }

    if (p == NULL) {
        log_info(LOG, "failed to bind socket");
        return 1;
    }

    //convert the src IP to a structure
    int ip_type = get_ip_type(myip);
    if (ip_type == -1){
        log_info(LOG, "You input a wrong IP address");
        exit(1);
    }

    struct sockaddr sa;
    if (ip_type == 0){//v4
        sa.sa_family = AF_INET;
    }else if (ip_type == 1){
        sa.sa_family = AF_INET6;
    }
    get_sockaddr(&sa, myip);

    //let the socket can listening on 68, and broadcast
    set_sock_opt(sockfd, &sa);

    //create the dhcp packet structure
    struct dhcp_packet out_packet;
    init_packet(&out_packet);

    //make the common parts, such as xid, ciadr et al.
    make_common_parts(&out_packet, myip, md);
    
    //make the options part, but right now only support domain name and private 246
    int offset;
    if(md->state == 0)
        offset = make_options(&out_packet, NACSERVER);
    else
        offset = make_options(&out_packet, NDNPARA);

    //DHCP_FIXED_NON_UDP includes all the field from op to the end of file, offset is the length of options
    int msg_len = DHCP_FIXED_NON_UDP + offset;
redo:
    memset(buf, 0, MAXBUFLEN);
    memcpy(buf, &out_packet, msg_len);

    log_info(LOG, "Send DHCPINFORM msg from %s to %s", myip, srv);
    //send packet
    if ((numbytes = sendto(sockfd, buf, msg_len, 0, p->ai_addr, p->ai_addrlen)) == -1) {
        log_info(LOG, "failed to send data");
        close(sockfd);
        return 1;
    }

    //read from remote server, maybe we need to get its addr for the latter use
    struct sockaddr_storage their_addr;
    socklen_t addr_len = sizeof their_addr;
wait:
    if(readable_time(sockfd, TIMEOUT) == 0){//need to add retry
        log_info(LOG, "timeout ...");
        if(retry < MAXRETRYTIME){
            retry ++;
            goto redo;
        }
    }else{
           memset(buf, 0, MAXBUFLEN);
        if ((numbytes = recvfrom(sockfd, buf, MAXBUFLEN, 0, (struct sockaddr *)&their_addr, &addr_len)) == -1) {
            log_info(LOG, "receive failed");
            close(sockfd);
            return 1;
        }

        //get src addr
        char src_addr[INET6_ADDRSTRLEN];

        if (their_addr.ss_family == AF_INET){
            struct sockaddr_in *sock = (struct sockaddr_in *)&their_addr;
            inet_ntop(AF_INET, &(sock->sin_addr), src_addr, INET_ADDRSTRLEN);
        }

        if (their_addr.ss_family == AF_INET6){
            struct sockaddr_in6 *sock = (struct sockaddr_in6 *)&their_addr;
            inet_ntop(AF_INET6, &(sock->sin6_addr), src_addr, INET6_ADDRSTRLEN);
        }

        struct dhcp_packet in_packet;
        init_packet(&in_packet);
        memcpy(&in_packet, buf, numbytes);

        //check if this message is the right ACK, on the same port, dhclient may send out request and get ack or offer
        if((rv = is_valid_inform_reply(&in_packet, numbytes, md)) == 0){
            log_info(LOG, "valid ACK message from %s", src_addr);
            rv = parse_options(&in_packet, md);
            if(rv != 0)//when md->state is 0, we need a timer to say if it is time out
                goto wait;
            //copy the packet to md
            if (md->state == NDNPARA){//unicast
                md->packet = (struct dhcp_packet *)malloc(sizeof(struct dhcp_packet));
                memcpy(md->packet, buf, numbytes);
                md->packet_len = numbytes;
            }
        }
        else{
            log_info(LOG, "invalid ACK message from %s", src_addr);
            goto wait;
        }
    }
    
//out:
    freeaddrinfo(servinfo);
    close(sockfd);

    if(retry >= MAXRETRYTIME)
        return 1;

    return 0;
}
*/

int
get_inform_nacs(struct mydata *md){
    int res;
    md->dst = BROADCAST;
    md->state = NACSERVER;//broadcast
    res = send_inform(md);
    return res;
}

void
init_list(struct mydata *md){
    free_nacs(md);
    free_gws(md);
    free_namespaces(md);
}

void
usage(char *program){
    printf("usage:%s <-a local_ip> <-p local_port> [-h display_help_info]\n"
         "\t-a the ip address on which %s is running\n"
         "\t-p the port number on which %s is running\n"
         "\t-h display this help message\n"
         , program, program, program);
    exit(1);
}

int main(int argc, char **argv)
{
    int sockfd;
    struct addrinfo hints, *servinfo, *p;
    int rv, res;
    int numbytes;
    struct sockaddr_storage their_addr;
    char buf[MAXBUFLEN];
    socklen_t addr_len;
    char s[INET6_ADDRSTRLEN];
    char *local_port = NULL;
    char *local_ip = NULL;
    LOG = stdout;

    while ((res = getopt(argc, argv, "a:p:h")) != -1) {
       switch (res){
           case 'a':
               local_ip = optarg;
               break;
           case 'p':
               local_port = optarg;
               break;
           case 'h':
           default:
               usage(argv[0]);
       }
    }

    if (local_port == NULL){
        usage(argv[0]);
    }

    //get the structure of the current ip
    struct in_addr ciaddr;
    if((rv = inet_aton(local_ip, &ciaddr)) == 0){
        log_info(LOG, "Cannot convert IP");
        exit(1);
    }

    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC; // set to AF_INET to force IPv4
    hints.ai_socktype = SOCK_DGRAM;
    hints.ai_flags = AI_PASSIVE; // use my IP

    if ((rv = getaddrinfo(NULL, local_port, &hints, &servinfo)) != 0) {
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
        return 1;
    }

    // loop through all the results and bind to the first we can
    for(p = servinfo; p != NULL; p = p->ai_next) {
        if ((sockfd = socket(p->ai_family, p->ai_socktype,
                p->ai_protocol)) == -1) {
            perror("listener: socket");
            continue;
        }

        if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
            close(sockfd);
            perror("listener: bind");
            continue;
        }

        break;
    }

    if (p == NULL) {
        fprintf(stderr, "listener: failed to bind socket\n");
        return 2;
    }

    freeaddrinfo(servinfo);

    //get local ip's structure
    struct sockaddr_in myip;
    inet_pton(AF_INET, local_ip, &(myip.sin_addr));

    //get the hardware address by ip
    //char face_name[INTERFACENAMELEN];
    //get_face_name(face_name, localIP);
    struct hardware user_hw;
    //get_hw_addr(face_name, &myhw);

    //add my code here
    struct mydata *md = (struct mydata *)malloc(sizeof (struct mydata));
    md->naclist = NULL;
    md->gws = NULL;
    md->namelist = NULL;
    md->packet = NULL;
    
    md->myip = local_ip;
    md->myport = local_port;
    md->hw = &user_hw;

    log_info(LOG, "listener: waiting to recvfrom...");

    while(1){
        addr_len = sizeof their_addr;
        if ((numbytes = recvfrom(sockfd, buf, MAXBUFLEN-1 , 0,
            (struct sockaddr *)&their_addr, &addr_len)) == -1) {
            perror("recvfrom");
            exit(1);
        }

        log_info(LOG, "listener: got packet from %s",
            inet_ntop(their_addr.ss_family,
                get_in_addr((struct sockaddr *)&their_addr),
                s, sizeof s));

        struct dhcp_packet in_packet;
        init_packet(&in_packet);
        memcpy(&in_packet, buf, numbytes);
        
        //cpy user's info
        //user_hw.hbuf[0] = in_packet.htype;
        //user_hw.hlen = in_packet.hlen + 1;
        //memcpy(&user_hw.hbuf[1], &(in_packet.chaddr), user_hw.hlen - 1);

        //generate the transaction no.
        md->my_xid = rand();

        res = get_inform_nacs(md);

        md->my_xid = in_packet.xid;
        //modify the IP of the packet and send it out directly
        in_packet.ciaddr = ciaddr;
        md->packet = &in_packet;
        md->packet_len = numbytes;

        if (res == 0)
            res = try_nacs(md);
        /* Send the message back to client */
        if (res == 0){
            if (sendto(sockfd, md->packet, md->packet_len, 0,
                       (struct sockaddr *) &their_addr,
                       addr_len) <= 0) {
                log_info(LOG, "Having issues in sending back");
            }
        }
 
        //return the packet directly
        if (md->packet != NULL){
            //free(md->packet);
            md->packet = NULL;
            md->packet_len = 0;
        }
        init_list(md);
    }
    free(md);
    close(sockfd);

    return 0;
}
